Flask 中的文件上传和下载
本文主要介绍了在 flask 中如何实现文件的上传和下载, 同时稍微深入的探寻了一下文件上传和下载的底层原理.
很久以前
概述
在 flask 中实现文件上传, 需要使用的是
request.files
- 在模板的定义 的
enctype=multipart/form-data
form
- 在 中定义
form
input[type=file, name=filename]
- 使用 来获取上传的文件
request.files[filename]
对象FileStorage
- 对象中,
FileStorage
为文件名, 使用filename
方法可以读取文件内容read
在 flask 中实现文件下载, 需要使用
send_file
- 构造/获取 需要被下载文件的
file pointer
- 以 作为返回响应
send_file(fp, as_attachment=True, attachment_filename=filename)
import tempfilefrom flask
import requests, send_file
def handler(content_raw):
# handle raw content in file
return content_raw
def read_and_download():
file = request.files.values()[0]
filename, file_content = file.filename, file.read()
content_handled = handler(file_content)
# handler use to handle file content
tmp = tempfile.TemporaryFile()
# construct a tmp file
tmp.write(content_handled)
# save handled content to tmp file
tmp.seek(0)
# reset file stream position
return send_file(tmp, as_attachment=True, attachment_filename="download.ext")
文件上传的原理
文件上传, 其本质是通过客户端(仅以浏览器为例)将一种数据传输到服务器(即通过 form 提交数据), 其特殊之处在于, 传输的数据类型是二进制类型数据
一般的情况下, 我们通过 form 提交数据, 要做的事情有两件:
- 定义提交的目的地, 通过指定 form 的 action 实现
- 定义提交的方法, 通过指定 form 的 method 实现
当服务器接收到来自浏览器的请求之后, 会根据请求头的内容对数据进行处理.
对于一般的 form 数据, 浏览器会默认为请求头添加
Content-Type: application/x-www-form-urlencoded
但是对于文件类型的数据我们必须要手动设置
Content-Type
- 设置 的
form
为enctype
multipart/form-data
设置之后, 请求头中会出现
Content-Type: multipart/form-data
multipart/form-data
通过这样的处理, 文件就能被服务器接收到并且进行正确的处理, 下面是一个基于 flask 的文件上传的请求头实例
在 flask 中, 对于 上传的文件数据 的获取主要是在
werkzeug
- flask/wrappers.py[L:168] -
Request._load_form_data
- werkzeug/wrappers.py[L:379 ~ L:385] -
parser.parse
- werkzeug/formparser.py[L:213] -
_parse_multipart
文件下载的原理
这里所说的文件下载并不是只将 flask 作为 client 进行下载, 而是指访问者可以从 flask server 下载文件
其实, 文件下载的本质是客户端(以浏览器为例)针对服务器响应的特殊处理.
我们看到的网页内容本质上都是服务器返回给浏览器的
数据
Content-Type
数据
例如, 如果在响应头中存在
Content-Type: text/html
那么一个可以下载文件的响应的请求头回事什么样的呢?
上图是一个简单的基于 flask 实现的文件下载的响应头, 图中红框标注的是要重点关注的地方.
Content-Type: application/octet-stream
通用的二进制
通用二进制
下载
Content-Disposition: attachment; filename="filename.ext"
设置
Content-Disposition
- 某些类型的二进制数据浏览器是可用进行渲染的(比如 json 或者 pdf), 因此浏览器的默认行为就不再是下载而是渲染(此时 的值为
Content-Disposition
, 因此对于如果希望总是触发下载操作, 就需要手动设置inline
为Content-Disposition
(即下载).attachment
- 默认的情况下, 浏览器触发的下载操作并不会对下载的文件进行命名, 因此需要手动进行命名, 即为 添加
Content-Disposition
.filename="filename"
Flask 中实现文件上传和下载的方法
在 flask 中实现文件的上传与下载并不复杂, 因为许多针对底层的操作 flask 已经封装好了.
实现文件上传
flask 中实现文件上传的步骤主要有四:
- 在 template 中定义 为
enctype
,multipart/form-data
为method
的 form 元素POST
- 在 form 添加 元素
input[type=file]
- 在 views 中定义处理 form action 的方法
- 在 view function 中 使用 获取文件并进行处理
request.files
下面是一个简单的例子(摘自 flask 官方文档并进行了一定的修改, 直接保存为 app.py 并运行即可)
from flask import request, Flask, redirect
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
print('No file part')
return redirect(request.url)
file = request.files['file']
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
print('No selected file')
return redirect(request.url)
return file.read().decode() # convert to str
return '''
<!doctype html>人
<title>Upload new File</title>
<h1>Upload new File</h1>
<form action="{{ url_for('upload') }}" method="POST" enctype=multipart/form-data>
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
</html>
'''
app = Flask(__file__)
app.add_url_rule('', 'index', upload_file, methods=('GET',))
app.add_url_rule('', 'download', upload_file, methods=('POST',))
app.run(debug=True)
flask.requests
MultiDict
input[type=file, name=name]
flask.requests[name]
上传的文件是 FileStorage 的实例,
FileStorage
werkzeug
request stream
request stream
比如, 获取文件名:
FileStorage().filename
FileStorage().content_length
FileStorage().read()
注意
- 是
FileStorage
的封装, 因此在stream
之后 pointer 就指向了read
的末尾, 此时再次stream
无法得到任何内容, 如果需要再次使用read
方法获取内容, 需要先 file.seek(0) 将 pointer 重新指向read
头部stream
实现文件下载
实现文件下载主要是利用 flask 提供的
send_file
import json
import tempfile
from flask import Flask, send_file
def download_file():
file = tempfile.TemporaryFile()
json.dump({'name': 'demo', 'age': 22}, file)
return send_file(file, as_attachment=True, attachment_filename='demo.json')
app = Flask(__file__)
app.add_url_rule('', 'download', download_file, methods=('GET,'))
app.run(debug=True)
如过希望下载文件, 则必须设置
as_attachment=True
attachment_filename=filename
需要注意的是,
send_file
file pointer
open
为此, 可以使用临时文件解决这个问题, 使用 python 标准库中的
tempfile
(Named)TemporaryFile
tempfile
注意:
-
不要以
context(如 with tempfile.Temporary() as wf: …) 的形式使用with
, 因为在这种情况下执行tempfile
(或类似操作)之后会自动关闭 fp 导致file.write
时遇到 的错误(有一点我无法确认, 在 flasksend_file
完成之后是否会自动 close file)send_file
send closed file
-
当执行完
(或类似操作)之后, 需要使用file.write
重置file.seek(0)
位置(原因在上一节最后有说明), 否则下载的文件将会是空文件stream
实例
Online File Converter 是一个基于 flask 实现的站点, 这个站点的作用是将用户上传的
csv
excel